#include "stdafx.h"
#include "MiniBasicImpl.h"
#include "Resource.h"
#include <stdexcept>
#include <shlwapi.h>
#include <boost/preprocessor/wstringize.hpp>
#include "Support.h"
#include "Environment.h"
#include "Lexecutioner.h"
#include "GlobalVariables.h"
#include <process.h>
#include <cstdlib>
#include <oleauto.h>

MiniBasicImpl::MiniBasicImpl() : pTypeInf(0), refCount(1)
{
    std::wstring typelibPath = GetFullTypeLibPath();
    ITypeLib* pTypeLib = 0;
    HRESULT hr = LoadTypeLib(typelibPath.c_str(), &pTypeLib);
    if(SUCCEEDED(hr))
    {
        hr = pTypeLib->GetTypeInfoOfGuid(IID_IMiniBasic, &pTypeInf);
        pTypeLib->Release();
    }
    if(FAILED(hr))
    {
        throw std::runtime_error("Failed to load type library");
    }
    InterlockedIncrement(&globalRefCount);
}

MiniBasicImpl::~MiniBasicImpl()
{
    pTypeInf->Release();
    InterlockedDecrement(&globalRefCount);
}

STDMETHODIMP MiniBasicImpl::QueryInterface(REFIID interfaceId, LPVOID* ppData)
{
    HRESULT hr = S_OK;
    if(ppData)
    {
        if(interfaceId == IID_IUnknown)
        {
            *ppData = static_cast<IUnknown*>(this);
        }
        else if(interfaceId == IID_IDispatch)
        {
            *ppData = static_cast<IDispatch*>(this);
        }
        else if(interfaceId == IID_IMiniBasic)
        {
            *ppData = static_cast<IMiniBasic*>(this);
        }
        else
        {
            *ppData = NULL;
            hr = E_NOINTERFACE;
        }
        if(*ppData)
        {
            AddRef();
        }
    }
    else
    {
        hr = E_POINTER;
    }
    return hr;
}

STDMETHODIMP_(ULONG) MiniBasicImpl::AddRef()
{
    return InterlockedIncrement(&refCount);
}

STDMETHODIMP_(ULONG) MiniBasicImpl::Release()
{
    ULONG count = InterlockedDecrement(&refCount);
    if(!count)
    {
        delete this;
    }
    return count;
}

STDMETHODIMP MiniBasicImpl::GetTypeInfoCount(UINT *pInfo)
{
    HRESULT hr = S_OK;
    if(pInfo)
    {
        *pInfo = 1;
    }
    else
    {
        hr = E_POINTER;
    }
    return hr;
}

STDMETHODIMP MiniBasicImpl::GetTypeInfo(UINT info, LCID, LPTYPEINFO* ppInf)
{
    HRESULT hr = S_OK;
    if(ppInf)
    {
        if(info == 0)
        {
            *ppInf = pTypeInf;
            pTypeInf->AddRef();
        }
        else
        {
            hr = DISP_E_BADINDEX;
        }
    }
    else
    {
        hr = E_POINTER;
    }
    return hr;
}

STDMETHODIMP MiniBasicImpl::GetIDsOfNames(REFIID, LPOLESTR* ppNames, UINT namesCount, LCID, DISPID* rgDispId)
{
    HRESULT hr = S_OK;
    if(ppNames)
    {
        if(namesCount)
        {
            hr = DispGetIDsOfNames(pTypeInf, ppNames, namesCount, rgDispId);
        }
    }
    else
    {
        hr = E_POINTER;
    }
    return hr;
}

STDMETHODIMP MiniBasicImpl::Invoke(DISPID dispIdMember, REFIID, LCID, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExceptInfo, UINT* pArgErr)
{
    HRESULT hr = S_OK;
    if(pDispParams)
    {
        hr = DispInvoke(this, pTypeInf, dispIdMember, wFlags, pDispParams, pVarResult, pExceptInfo, pArgErr);
    }
    else
    {
        hr = E_POINTER;
    }
    return hr;
}

std::wstring MiniBasicImpl::GetFullTypeLibPath()
{
    WCHAR filepath[MAX_PATH] = {0};
    HMODULE hMod = MiniBasic::GetDllModuleHandle();
    if(hMod)
    {
        GetModuleFileNameW(hMod, filepath, MAX_PATH);
        std::wostringstream typeLibPath;
        typeLibPath << filepath << L"\\"BOOST_PP_WSTRINGIZE(TYPELIB_RESOURCE);
        return typeLibPath.str();
    }
    else
    {
        throw std::runtime_error("Failed to get module address");
    }
}

STDMETHODIMP MiniBasicImpl::ExecuteFile(ExecutionParameters* pExParams)
{
    HRESULT hr = S_OK;
    if(pExParams)
    {
        hr = ExecuteFileImpl(pExParams);
    }
    else
    {
        hr = E_POINTER;
    }
    return hr;
}
STDMETHODIMP MiniBasicImpl::ExecuteFileAsync(ExecutionParameters* pExParams, UINT_PTR* pHandle)
{
    HRESULT hr = S_OK;
    if(pExParams && pHandle)
    {
        hr = ExecuteFileImpl(pExParams, pHandle);
    }
    else
    {
        hr = E_POINTER;
    }
    return hr;
}

STDMETHODIMP MiniBasicImpl::ExecuteFileImpl(ExecutionParameters* pExParams, UINT_PTR* pHandle)
{
    HRESULT hr = S_OK;
    if(PathFileExistsW(pExParams->pathToFile))
    {
        if(!(pExParams->memoryAreaSize)) pExParams->memoryAreaSize = epMemoryAreaDefault;
        NewThreadParams tp;
        tp.pExParams = pExParams;
        if(pHandle)
        {
            tp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
            if(tp.hEvent)
            {
                uintptr_t hThread = _beginthreadex(NULL, 0, &DoFileExecutionFunc, &tp, 0, NULL);
                if(hThread)
                {
                    WaitForSingleObject(tp.hEvent, INFINITE);
                    CloseHandle(tp.hEvent);
                    if(SUCCEEDED(hr = tp.hr))
                    {
                        *pHandle = static_cast<UINT_PTR>(hThread);
                    }
                    else
                    {
                        *pHandle = 0;
                        CloseHandle(reinterpret_cast<HANDLE>(hThread));
                    }
                }
                else
                {
                    hr = HRESULT_FROM_WIN32(_doserrno);
                }
            }
            else
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }
        else
        {
            tp.hEvent = 0;
            hr = static_cast<HRESULT>(DoFileExecutionFunc(&tp));
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    }
    return hr;
}

STDMETHODIMP MiniBasicImpl::ExecuteString(ExecutionParameters* pExParams, SAFEARRAY* pArray, VARIANT_BOOL hasTextLineNumbers)
{
    HRESULT hr = S_OK;
    if(pArray)
    {
        VARTYPE vt = VT_EMPTY;
        if(SUCCEEDED(SafeArrayGetVartype(pArray, &vt)) && (vt == VT_UI1))
        {
            char* data = NULL;
            if(SUCCEEDED(hr = SafeArrayAccessData(pArray, reinterpret_cast<void**>(&data))))
            {
                LONG upperBound = 0, lowerBound = 0, dataLength = 0;
                if(SUCCEEDED(hr = SafeArrayGetUBound(pArray, 1, &upperBound)) &&
                    SUCCEEDED(hr = SafeArrayGetLBound(pArray, 1, &lowerBound))
                )
                {
                    dataLength = (upperBound - lowerBound) + 1;
                    ExecutionParameters params = {NULL, epMemoryAreaDefault, false};
                    ExecutionParameters* pParamsToUse = pExParams ? pExParams : &params;
                    pParamsToUse->memoryAreaSize = std::max<SIZE_T>(epMemoryAreaDefault, pParamsToUse->memoryAreaSize);
                    std::auto_ptr<MiniBasic::Environment> env;
                    if(SUCCEEDED(hr = MiniBasic::CreateExecutionEnvironment(pParamsToUse, env)))
                    {
                        try
                        {
                            MiniBasic::Lexecutioner lexer(data, dataLength, !!hasTextLineNumbers);
                            hr = env->DoExecution(lexer);
                        }
                        catch(HRESULT hrFail)
                        {
                            hr = hrFail;
                        }
                    }
                    SafeArrayUnaccessData(pArray);
                }
            }
        }
        else
        {
            hr = DISP_E_BADVARTYPE;
        }
    }
    else
    {
        hr = E_POINTER;
    }
    return hr;
}

unsigned WINAPI MiniBasicImpl::DoFileExecutionFunc(LPVOID params)
{
    MiniBasicImpl::NewThreadParams* pThreadParams = static_cast<MiniBasicImpl::NewThreadParams*>(params);
    HRESULT hrLocal = S_OK;
    HRESULT& hr = pThreadParams->hEvent ? pThreadParams->hr : hrLocal;
    std::auto_ptr<MiniBasic::Environment> env;
    if(SUCCEEDED(hr = MiniBasic::CreateExecutionEnvironment(pThreadParams->pExParams, env)))
    {
        try
        {
            MiniBasic::Lexecutioner lexer(pThreadParams->pExParams->pathToFile);
            if(pThreadParams->hEvent)
            {
                SetEvent(pThreadParams->hEvent);
            }
            hrLocal = env->DoExecution(lexer);
        }
        catch(HRESULT hrFail)
        {
            hr = hrFail;
            if(pThreadParams->hEvent)
            {
                SetEvent(pThreadParams->hEvent);
            }
        }
    }
    return static_cast<unsigned>(hrLocal);
}
